/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "mysql_connection.h"

#include "node/sys/lock_guard.h"
#include "node/util/string.h"
#include "mysql_resultset.h"
#include "mysql_connection.h"
#include <cassert>
using Tokens = ::node::util::Tokens;
using ResultSet = ::node::rdb::ResultSet;
using MySQLResultSet = ::node::rdb::MySQLResultSet;

#define __NODE_LOCK(x)  ::node::sys::LockGuard<::node::sys::Mutex> guard(x)

namespace node {
    namespace rdb {

        MySQLConnection::MySQLConnection(void)
            : Connection()
            , my_(NULL)
            , my_lock_()
        {

        }

        MySQLConnection::~MySQLConnection(void)
        {
            if (my_)
                mysql_close(my_);
        }

        bool MySQLConnection::Open(const ::std::string &host,
            const ::std::string &port, const ::std::string &user, 
            const ::std::string &pass, const ::std::string &db, bool auto_reconnect /*= true*/)
        {
            my_ = mysql_init(NULL);

            do
            {
                int _port;
                const char* unix_socket;

                mysql_options(my_, MYSQL_SET_CHARSET_NAME, "utf8");
                mysql_options(my_, MYSQL_OPT_RECONNECT, "1");

#if defined(__WINDOWS__)
                if (host == ".")
                {
                    unsigned int opt = MYSQL_PROTOCOL_PIPE;
                    mysql_options(my_, MYSQL_OPT_PROTOCOL, &opt);
                    _port = 0;
                    unix_socket = NULL;
                }
                else
                {
                    _port = atoi(port.c_str());
                    unix_socket = NULL;
                }
#elif defined(__LINUX__)
                if (host == ".")
                {
                    unsigned int opt = MYSQL_PROTOCOL_SOCKET;
                    mysql_options(my_, MYSQL_OPT_PROTOCOL, &opt);
                    port = 0;
                    unix_socket = port_or_socket.c_str();
                }
                else
                {
                    port = atoi(port_or_socket.c_str());
                    unix_socket = NULL;
                }
#endif

                if (auto_reconnect) {
                    char value = 1;
                    mysql_options(my_, MYSQL_OPT_RECONNECT, &value);
                }

                if (!mysql_real_connect(my_, host.c_str(), user.c_str(),
                    pass.c_str(), db.c_str(), _port, unix_socket, 0))
                {
                    errno_ = mysql_errno(my_);
                    err_message_ = mysql_error(my_);
                    mysql_close(my_);
                    my_ = NULL;
                    break;
                }

            } while (false);

            return my_ != NULL;
        }

        bool MySQLConnection::Open(const ::std::string &info, bool auto_reconnect /*= true*/)
        {
            Tokens tokens = ::node::util::StrSplit(info, ";");
            Tokens::iterator iter = tokens.begin();

            std::string host, port_or_socket, user, password, database;

            if (iter != tokens.end())
                host = *iter++;
            if (iter != tokens.end())
                port_or_socket = *iter++;
            if (iter != tokens.end())
                user = *iter++;
            if (iter != tokens.end())
                password = *iter++;
            if (iter != tokens.end())
                database = *iter++;

            return MySQLConnection::Open(host, port_or_socket, user, password, database, auto_reconnect);
        }

        void MySQLConnection::Close(void)
        {
            if (my_ != NULL)
            {
                mysql_close(my_);
                my_ = NULL;
            }
        }

        ::node::rdb::ResultSet *MySQLConnection::Query(const ::std::string &sql)
        {
            __NODE_LOCK(my_lock_);
            if (!mysql_query(my_, sql.c_str()))
            {
                error_ = false;
                MYSQL_RES* res = mysql_store_result(my_);
                if (res != NULL)
                {
                    ResultSet *rs = new MySQLResultSet(res);
                    if (rs && rs->NextRow()) {
                        return rs;
                    }
                    delete rs;
                }
                return NULL;
            } 
            error_ = true;
            errno_ = mysql_errno(my_);
            err_message_ = mysql_error(my_);
            return NULL;
        }

        bool MySQLConnection::Execute(const ::std::string &sql)
        {
            __NODE_LOCK(my_lock_);
            if (mysql_query(my_, sql.c_str())) {
                error_ = true;
                errno_ = mysql_errno(my_);
                err_message_ = mysql_error(my_);
                return false;
            }
            error_ = false;
            return true;
        }

        void MySQLConnection::Ping(void)
        {
            assert(my_);
            __NODE_LOCK(my_lock_);
            mysql_ping(my_);
        }

    }
}